Keygenme - JuLio eL NeGRo [zip]

Dans ce tutoriel, nous allons étudier comment transformer un programme en son propre keygener quand vous avez un peu de mal à le keygener, ou si vous n'avez pas vraiment le temps. On exécute le fichier exécutable, on clique sur le bouton 'Check' et une fenêtre apparaît avec comme message 'Wrong Serial !'.

On va donc chercher toute relation avec ce message, comment apparaît-il, etc... On ouvre alors le fichier exécutable avec IDA (je n'utilise pas W32Dasm tout simplement parce qu'il ne fonctionne pas chez moi, et IDA est très pratique, pour cela allez voir les tutz de Netix sur IDA).

On va dans le menu 'Search' puis 'text...', on entre 'Wrong Serial !' dans le champ, puis 'OK'.
On arrive alors à cette ligne :

.data:004060AF
aWrongSerial db 'Wrong Serial !',0 ; DATA XREF: sub_4043A3+66|o

En jaune nous avons l'adresse en mémoire dans le programme, ensuite le nom de la variable, le type de la variable, puis la valeur affectée à la variable. En commentaire, IDA nous indique dans quel endroit du programme cette variable est appelée. On double-clique donc sur sub_4043A3+66, et on arrive ici :

.text:00404402 push 0 ...................; uType
.text:00404404 push offset aError .......; lpCaption
.text:00404409 push offset aWrongSerial .; lpText
.text:0040440E push [ebp+hWnd] ; hWnd
.text:00404411 call MessageBoxA

On voit bien ici l'appel à la MessageBox !
Nous avons en paramètre de la MessageBox le type (ici juste le bouton OK), l'adresse du texte de la barre de titre, l'adresse du texte de la boîte, et enfin le handle du programme pour attacher la boîte au programme.
On a déjà une idée où poser un breakpoint. On retient donc l'adresse 00404409.

On exécute OllyDbg et on ouvre le crackme, ensuite on fait soit un clic-droit, 'Go to' et 'Expression', soit Ctrl+G, on entre l'adresse et 'OK'. On voit clairement les 2 appels MessageBox selon le résultat de la vérification.
On remonte légèrement, et voici ce qu'on observe :

004043F4 |. E8 51000000 ..CALL Keygenm.0040444A
004043F9 |. E8 AB000000 ..CALL Keygenm.004044A9
004043FE |. 85C0 .........TEST EAX,EAX
00404400 |. 75 16 ........JNZ SHORT Keygenm.00404418
00404402 |. 6A 00 ........PUSH 0 ........................; /Style = MB_OK|MB_APPLMODAL
00404404 |. 68 BE604000 ..PUSH Keygenm.004060BE .........; |Title = "error"
00404409 |. 68 AF604000 ..PUSH Keygenm.004060AF .........; |Text = "Wrong Serial !"
0040440E |. FF75 08 ......PUSH DWORD PTR SS:[EBP+8] .....; |hOwner
00404411 |. E8 0A010000 ..CALL <JMP.&USER32.MessageBoxA> ; \MessageBoxA
00404416 |. EB 14 ........JMP SHORT Keygenm.0040442C
00404418 |> 6A 00 ........PUSH 0 ........................; /Style = MB_OK|MB_APPLMODAL
0040441A |. 68 A7604000 ..PUSH Keygenm.004060A7 .........; |Title = "success"
0040441F |. 68 7C604000 ..PUSH Keygenm.0040607C .........; |Text = "Good Job, ..."
00404424 |. FF75 08 ......PUSH DWORD PTR SS:[EBP+8] .... ; |hOwner
00404427 |. E8 F4000000 ..CALL <JMP.&USER32.MessageBoxA> ; \MessageBoxA


Juste au-dessus le premier call MessageBox le programme effectue un test en 004043FE, on peut supposer qu'il s'agit d'un test qui permet de diriger vers le bon message. En effet si on regarde le code, 'test eax, eax' permet de vérifier si le registre eax est à 0, s'il n'y est pas on saute en 00404418, or à cette adresse c'est le call qui affiche le test de réussite ! Si on désirait cracker le prog il faudrait remplacer le JNZ par un JMP qui ferait sauter le programme quelque soit le numéro de série entré. Donc en 004044A9 on vérifie si le numéro entré est correct... Mais à quoi correspond le call juste au-dessus ? Eh bien allons voir. On clique à l'adresse 004043F4 et on pose le breakpoint en tapant F2, on relance le prog en faisant Ctrl+F2, on fait 'Oui', F9, le bouton 'Check' du crackme et nous voilà sur cette ligne. Pour rentrer dans le call on fait F7, et on arrive ici :

0040444A /$ 6A 32 . . .. .PUSH 32 ..........................; /Count = 32 (50.)
0040444C |. 68 6C614000
. PUSH Keygenme.0040616C ...........; |Buffer = Keygenme.0040616C
00404451 |. FF35 A8684000 PUSH DWORD PTR DS:[4068A8] .......; |hWnd = 000502BC
00404457 |. E8 AC000000
. CALL <JMP.&USER32.GetWindowTextA> ; \GetWindowTextA

On cherche donc ici à récupérer un des champs rentré dans un buffer (adresse où on stockera les données récupérées) à l'adresse 0040616C de taille maximale 50 caractères (32 en hexadécimal). En faisant F8 et en allant voir à l'adresse 0040616C, on remarque qu'il charge en fait notre nom (pour cela faîtes un dump de la section .data sous OllyDbg et allez à l'adresse voulue. Ensuite le programme génère un numéro de série correspondant au nom entré :

0040445C |. 0BC0 . . .. . OR EAX,EAX
0040445E |. 74 48
. . .. .JE SHORT Keygenme.004044A8
00404460 |. 68 6C614000
. PUSH Keygenme.0040616C ............; ASCII "Le_MaLaDe"
00404465 |. 50
. . . . . .PUSH EAX
00404466 |. 68 6C654000
. PUSH Keygenme.0040656C
0040446B |. E8 90CBFFFF
..CALL Keygenme.00401000
00404470 |. BE 6C654000
..MOV ESI,Keygenme.0040656C
00404475 |. 33C9
. . . . .XOR ECX,ECX
00404477 |. 33C0
. . . . .XOR EAX,EAX
00404479 |> 33DB
. . . . .XOR EBX,EBX
0040447B |. 8A1C31
. . . .MOV BL,BYTE PTR DS:[ECX+ESI]
0040447E |. 84DB
. . . . .TEST BL,BL
00404480 |. 74 0E
. . . . JE SHORT Keygenme.00404490
00404482 |. 0FAFD9
. . . .IMUL EBX,ECX
00404485 |. 0FAFDB
. . . .IMUL EBX,EBX
00404488 |. 83C3 50
. . . ADD EBX,50
0040448B |. 03C3
. . . . .ADD EAX,EBX
0040448D |. 41
. . . . . .INC ECX
0040448E |.^EB E9
. . . . JMP SHORT Keygenme.00404479
00404490 |> 35 78563412
. XOR EAX,12345678
00404495 |. 50 PUSH EAX .....................................; /<%lu>
00404496 |. 68 78604000
. PUSH Keygenme.00406078 ............; |Format = "%lu"
0040449B |. 68 6C674000
. PUSH Keygenme.0040676C ............; |s=Keygenme.0040676C
004044A0 |. E8 45000000
. CALL <JMP.&USER32.wsprintfA> ......; \wsprintfA
004044A5 |. 83C4 0C
......ADD ESP,0C
004044A8 \> C3
...........RETN
004044DA |. 47............INC EDI

La première instruction vérifie si le nombre de caractères lus est 0, si oui on quitte le call, sinon on continue.
On appelle une fonction avec comme arguments le nom entré, le nombre de caractères du nom, puis une adresse. Après le call, on place dans le registre ESI l'adresse utilisée par le call, il s'agissait en fait d'un buffer.

On initialise ECX et EAX. On initialise ensuite EBX à 0, on va boucler sur la chaîne obtenue précédemment.
On effectue quelques opérations pour chaque caractère lu, puis on formate la chaîne ainsi obtenue en entier.
On sort du call, vous verrez pourquoi par la suite pourquoi je n'explique pas la routine.
On rentre dans le call qui suit, qui normalement vérifie le numéro de série :

004044A9 /$ 6A 32 ........PUSH 32 ..........................; /Count = 32 (50.)
004044AB |. 68 6C634000 ..PUSH Keygenme.0040636C ...........; |Buffer = Keygenme.0040636C
004044B0 |. FF35 A4684000 PUSH DWORD PTR DS:[4068A4]........; |hWnd = 000602C4
004044B6 |. E8 4D000000 ..CALL <JMP.&USER32.GetWindowTextA> ; \GetWindowTextA
004044BB |. 0BC0 .........OR EAX,EAX
004044BD |. 74 29 ........JE SHORT Keygenme.004044E8
004044BF |. 33C9 .........XOR ECX,ECX
004044C1 |. 33D2 .........XOR EDX,EDX
004044C3 |. 33DB .........XOR EBX,EBX
004044C5 |. 33FF .........XOR EDI,EDI
004044C7 |> 8B91 6C634000 MOV EDX,DWORD PTR DS:[ECX+40636C]
004044CD |. 8B99 6C674000 MOV EBX,DWORD PTR DS:[ECX+40676C]
004044D3 |. 33D3 .........XOR EDX,EBX
004044D5 |. 75 0F ........JNZ SHORT Keygenme.004044E6
004044D7 |. 83C1 04 ......ADD ECX,4
004044DA |. 47............INC EDI
004044DB |. 3BF8 .........CMP EDI,EAX
004044DD |.^7C E8 ........JL SHORT Keygenme.004044C7
004044DF |. B8 01000000 ..MOV EAX,1
004044E4 |. EB 02 ........JMP SHORT Keygenme.004044E8
004044E6 |> 33C0 .........XOR EAX,EAX
004044E8 \> C3 ...........RETN

Et là bingo il s'agit bien de notre routine de vérif', d'ailleurs on peut voir à la fin un 'mov eax, 1', et un 'xor eax, eax', d'où le 'test eax, eax' juste après !

On parcourt donc le bon numéro de série qui se situe à l'adresse 0040676C et celui entré qui est à l'adresse 40636C, on prend les 4 premiers chiffres, on fait un xor sur les 2 séries de 4 chiffres prises, et si le résultat est 0 c'est-à-dire les 2 séries identiques donc serial valide, on prend les 4 chiffres suivants, etc...

Si on est arrivé jusqu'au bout de la boucle sans sortir, on met EAX à 1 (numéro de série valide), sinon EAX est à 0
:-((( Ok, on sait donc où se trouve notre numéro de série valide, mais dans tout ça, comment se génère-t-il ?

Ben c'est là qu'on va se servir du reversing plutôt que de keygener à proprement dit.

Si on veut observer comment se génère le premier numéro on entre dans le call 00401000 qui se situe à l'adresse 0040446B, et là que voit-on ? A peu près 3000 lignes d'assembleur... Alors bon, j'ai cherché à comprendre au début, mais quand j'ai vu qu'il y avait 3000 lignes comme ça, je me suis dit, bon on verra plus tard pour le keygener, et là m'est venu une idée, oui oui. On sait donc que le bon numéro de série se trouve à l'adresse 0040676C, et quand on rentre un numéro de série au hasard on a toujours le message d'erreur (à moins de tomber par hasard dessus mais alors là je serais sur le cul franchement !).

Quand on fait notre appel à MessageBox, on a vu que les messages étaient passé en argument par un push, et si au lieu d'afficher le message d'erreur on affichait le numéro de série valide ? Puis ça change de toujours keygener en plus. Hum, essayons, revenons donc à l'adresse 00404409 :

00404402 |. 6A 00 ........PUSH 0 ........................; /Style=MB_OK|MB_APPLMODAL
00404404 |. 68 BE604000 ..PUSH Keygenm.004060BE .........; |Title = "error"
00404409 |. 68 AF604000 ..PUSH Keygenm.004060AF .........; |Text = "Wrong Serial !"
0040440E |. FF75 08 ......PUSH DWORD PTR SS:[EBP+8] .....; |hOwner
00404411 |. E8 0A010000 ..CALL <JMP.&USER32.MessageBoxA> ; \MessageBoxA

On doucle-clique sur 'PUSH Keygenm.004060AF' pour pouvoir changer le code assembleur, on le remplace par 'PUSH 40676C', on clique sur 'Assemble', on continue l'exécution du programme en faisant F9, on entre notre nom, 'Check', et que voit-on ? Plus de message d'erreur mais le numéro qui apparaît :o)))

On prend donc notre éditeur hexa, on note la modification à effectuer ici '68AF604000'.
Dans Hex Workshop on fait 'Edit', 'Find...', puis on choisit 'Hex Values', on entre notre valeur, puis 'OK'.

Quand on a modifié notre code dans OllyDbg, le nouveau code est apparu en rouge :

00404402 |. 6A 00 ........PUSH 0 ........................; /Style=MB_OK|MB_APPLMODAL
00404404 |. 68 BE604000 ..PUSH Keygenm.004060BE .........; |Title = "error"
00404409 |. 68 6C674000 ..PUSH Keygenm.0040676C .........; |Text = "Wrong Serial !"
0040440E |. FF75 08 ......PUSH DWORD PTR SS:[EBP+8] .....; |hOwner
00404411 |. E8 0A010000 ..CALL <JMP.&USER32.MessageBoxA> ; \MessageBoxA

On remplace donc les octets 'AF60' par '6C67', on sauvegarde, et le tour est joué !
On exécute notre programme modifié, on rentre le nom, 'Check', et le message d'erreur s'affiche avec notre numéro de série correspondant, on le note, on entre ce numéro, on réessaie, et le bon message s'affiche !